package cn.calm.xhtml.handler;

import static cn.calm.xhtml.XhtmlActivator.logError;
import static cn.calm.xhtml.XhtmlActivator.logInfo;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.util.ValidationEventCollector;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.osgi.framework.Bundle;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import cn.calm.xhtml.XhtmlActivator;
import cn.calm.xhtml.entity.Namespace;
import cn.calm.xhtml.entity.Tag;

public class NamespaceHandler {
	private final static XPathExpression namespaceexpression;
	private final static ExecutorService executorservice = Executors
			.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
	static {
		try {
			XPathFactory factory = XPathFactory.newInstance();
			XPath xpath = factory.newXPath();
			namespaceexpression = xpath.compile("namespace-uri(/*)");
		} catch (XPathExpressionException ex) {
			throw new RuntimeException(ex);
		}

	}
	private static Unmarshaller unmarshaller;
	static {
		try {
			JAXBContext context = JAXBContext.newInstance(Namespace.class);
			unmarshaller = context.createUnmarshaller();
			String schemaLanguage = XMLConstants.W3C_XML_SCHEMA_NS_URI;
			SchemaFactory schemaFactory = SchemaFactory
					.newInstance(schemaLanguage);
			ValidationEventCollector vec = new ValidationEventCollector();
			URL resource = NamespaceHandler.class
					.getResource("/cn/calm/xhtml/entity/ext.xsd");
			if (resource == null) {
				Bundle bundle = XhtmlActivator.getDefault().getBundle();
				resource = bundle.getResource("/cn/calm/xhtml/entity/ext.xsd");
			}
			Schema schema = schemaFactory.newSchema(resource);
			unmarshaller.setSchema(schema);
			unmarshaller.setEventHandler(vec);
		} catch (JAXBException | SAXException e) {
			logError(e.getMessage());
		}
	}

	private static final String DIALECT_EXTRAS_NAMESPACE = "http://www.calm.cn/extras/tag";

	public static void parseProject(IJavaProject javaProject)
			throws JavaModelException {
		IProject project = javaProject.getProject();
		ArrayList<Future<List<URL>>> scannertasks = new ArrayList<>();
		File workSpace = project.getLocation().toFile().getParentFile();
		for (IPackageFragmentRoot packagefragmentroot : javaProject
				.getAllPackageFragmentRoots()) {
			scannertasks.add(executorservice.submit(new StorageCallable(
					packagefragmentroot, workSpace)));

		}
		for (Future<List<URL>> future : scannertasks) {
			List<URL> list;
			try {
				list = future.get();
				if (list.isEmpty()) {
					continue;
				}
				logInfo(list.size()+"");
				for (URL path : list) {
					executorservice.submit(new NamespaceCallable(path,unmarshaller,project)).get();
				}
			} catch (InterruptedException | ExecutionException e1) {
				e1.printStackTrace();
			}

		}
	}

	public static void deparseProject(IJavaProject project) {
		Map<IProject, Map<String, Set<Tag>>> namespaces = XhtmlActivator
				.getNamespaces();
		namespaces.remove(project.getProject());
	}

	/**
	 * Returns whether or not the given resource is a Thymeleaf dialect help XML
	 * file.
	 * 
	 * @param resource
	 * @return <tt>true</tt> if the resource is an XML file in the
	 *         <tt>http://www.thymeleaf.org/extras/dialect</tt> namespace.
	 */
	static boolean isDialectHelpXMLFile(URL resource) {

		InputStream resourcestream = null;
		try {

			// Check if the XML file namespace is correct
			// String osString = resource.getFullPath().toOSString();
			// logInfo(osString);
			String namespace = namespaceexpression.evaluate(new InputSource(resource.openStream()));
			if (namespace.equals(DIALECT_EXTRAS_NAMESPACE)) {
				return true;
			}
			return false;
		} catch (XPathExpressionException | IOException ex) {
			logError("Unable to execute XPath expression", ex);
			return false;
		} finally {
			if (resourcestream != null) {
				try {
					resourcestream.close();
				} catch (IOException ex) {
					throw new RuntimeException(ex);
				}
			}
		}
	}

	public static void main(String[] args) throws XPathExpressionException,
			JAXBException, SAXException {
		InputStream resourceAsStream = NamespaceHandler.class
				.getResourceAsStream("/cn/calm/xhtml/entity/Web.xml");
		Object unmarshal = unmarshaller.unmarshal(resourceAsStream);
		logInfo(unmarshal+"");
	}
}

class StorageCallable implements Callable<List<URL>> {
	private IPackageFragmentRoot packagefragmentroot;
	private File workSpace;

	public StorageCallable(IPackageFragmentRoot packagefragmentroot,
			File workSpace) {
		super();
		this.packagefragmentroot = packagefragmentroot;
		this.workSpace = workSpace;
	}

	private ArrayList<URL> dialectfilepaths = new ArrayList<URL>();

	@SuppressWarnings("resource")
	@Override
	public List<URL> call() throws Exception {

		int elementType = packagefragmentroot.getKind();
		Path path = (Path) packagefragmentroot.getPath();
		File file;
		if (elementType == IPackageFragmentRoot.K_SOURCE) {
			file = new File(workSpace, path.toOSString());
		} else {
			file = path.toFile();
		}
		if (file.isDirectory()) {
			File namesp = new File(file, "META-INF/namespace");
			if (namesp.isDirectory()) {
				File[] listFiles = namesp.listFiles(new FileFilter() {

					@Override
					public boolean accept(File arg0) {
						return arg0.getName().endsWith("xml");
					}
				});
				for (File f : listFiles) {
					try {
						URL url = f.toURI().toURL();
						if (NamespaceHandler.isDialectHelpXMLFile(url)) {
							dialectfilepaths.add(url);
						}
					} catch (MalformedURLException e) {
						e.printStackTrace();
					}
				}
			}
		} else {
			try {
				ZipFile jar = new ZipFile(file);
				Enumeration<? extends ZipEntry> entries = jar.entries();
				while (entries.hasMoreElements()) {
					ZipEntry nextElement = entries.nextElement();
					String name = nextElement.getName();
					if (name.startsWith("META-INF/namespace")
							&& name.endsWith("xml")) {
						URL u = new URL("jar:"
								+ file.toURI().toURL().toString() + "!/" + name);
						if (NamespaceHandler.isDialectHelpXMLFile(u)) {
							dialectfilepaths.add(u);
						}
					}
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return dialectfilepaths;
	}
}

class NamespaceCallable implements Callable<Void> {
	private URL url;
	private Unmarshaller unmarshaller;
	private IProject project;

	public NamespaceCallable(URL url, Unmarshaller unmarshaller,
			IProject project) {
		super();
		this.unmarshaller = unmarshaller;
		this.url = url;
		this.project = project;
	}

	@Override
	public Void call() throws Exception {
		try {
			Namespace namespace = (Namespace) unmarshaller.unmarshal(url);
			XhtmlActivator.logInfo(url.toString());
			Map<IProject, Map<String, Set<Tag>>> namespaces = XhtmlActivator
					.getNamespaces();
			Map<String, Set<Tag>> map = namespaces.get(project);
			if (map == null) {
				map = new HashMap<>();
				namespaces.put(project, map);
			}
			Set<Tag> tags = map.get(namespace.getUri());
			if (tags == null) {
				tags = new HashSet<>();
				map.put(namespace.getUri(), tags);
			}
			tags.addAll(namespace.getTags());
		} catch (JAXBException e) {
			XhtmlActivator.logError(e.getMessage());
		} finally {
		}
		return null;
	}
}
